﻿using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Threading;
using System.IO.IsolatedStorage;
using System.IO;
using System.Xml.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Skyper.Model {
	public sealed class SkypeModel : BaseModel {
		#region Fields
		private readonly List<ContactModel> contacts;
		private readonly List<ChatModel> chats;
		private readonly Dictionary<ContactModel, ObservableCollection<MessageModel>> messagesCache;
		private readonly Dictionary<String, ImageSource> avatars;
		private readonly SkypeSync sync;
		private readonly SkypeConfig config;
		private readonly Dictionary<ContactModel, List<Action<ObservableCollection<MessageModel>>>> messagesContainer = new Dictionary<ContactModel, List<Action<ObservableCollection<MessageModel>>>>();
		#endregion
		#region Constructor
		public SkypeModel() {
			this.contacts = new List<ContactModel>();
			this.chats = new List<ChatModel>();
			this.messagesCache = new Dictionary<ContactModel, ObservableCollection<MessageModel>>(ContactModel.DefaultComparer);
			this.avatars = new Dictionary<String, ImageSource>();
			this.config = new SkypeConfig();

			this.sync = new SkypeSync();
			this.sync.SyncedContacts += new Action<ContactModel[]>(sync_SyncedContacts);
			this.sync.SyncedAvatars += new Action<Dictionary<string, Stream>>(sync_SyncedAvatars);
			this.sync.SyncedMessages += new Action<ContactModel, List<MessageModel>>(sync_SyncedMessages);
		}
		#endregion
		#region Private
		private void sync_SyncedContacts(ContactModel[] obj) {
			//This is only available contact list, if there is contact in available, and isn't in this list, this means other contact when offline.
			var data = new List<ContactModel>(obj);
			var comp = new ContactModel.FullnameComparer();
			//Add new contacts to available list, and to contact list
			foreach (var c in data) {
				int index = this.contacts.BinarySearch(c, comp);

				// Returns:
				//     The zero-based index of item in the sorted System.Collections.Generic.List<T>,
				//     if item is found; otherwise, a negative number that is the bitwise complement
				//     of the index of the next element that is larger than item or, if there is
				//     no larger element, the bitwise complement of System.Collections.Generic.List<T>.Count.
				if (index >= 0) {
					if (c.Available) {
						this.contacts[index].Update(c);
					} else {
						this.contacts.RemoveAt(index);
					}
				} else {
					index = ~index;

					if (this.contacts.Count == index) {
						this.contacts.Add(c);
					} else {
						this.contacts.Insert(index, c);
					}
				}
			}
			

			this.contacts.Sort(comp);
			if (App.HasDispatcher) {
				App.Dispatcher.BeginInvoke(() => {
					this.OnPropertyChanged("Contacts");
					App.Skype.OnConnectionSuccess();
				});
			}
		}
		private void sync_SyncedAvatars(Dictionary<String, Stream> obj) {
			if (App.HasDispatcher) {
				App.Dispatcher.BeginInvoke(new Action<Dictionary<String, Stream>>(ReloadAvatars), obj);
			}
		}
		private void sync_SyncedMessages(ContactModel arg1, List<MessageModel> arg2) {

		}
		private void ReloadAvatars(Dictionary<String, Stream> obj) {
			foreach (var pair in obj) {
				var contact = GetContact(pair.Key);
				if (contact != null) {
					contact.SetAvatar(pair.Value);
				}
			}
		}


		private void OnConnectionSuccess() {
			this.IsLoggedOn = true;
			if (this.ConnectionSuccess != null) {
				this.ConnectionSuccess(this, EventArgs.Empty);
			}
		}
		private void OnConnectionFailure() {
			if (this.ConnectionFailure != null) {
				this.ConnectionFailure(this, EventArgs.Empty);
			}
		}
		#endregion
		#region Public
		public void Save() {
			this.config.Save();
			this.sync.Save();
		}
		public void Load() {
			this.config.Load();
			this.sync.Load();
		}
		public void Sync() {
			this.sync.Start();
		}
		[System.Diagnostics.DebuggerNonUserCode()]
		public void DebugData() {
			if (!App.IsDesignTime()) {
				return;
			}
			Random r = new Random();
			int max = r.Next(5, 25);
			for (int i = 0; i < max; i++) {
				ContactModel model = new ContactModel();
				model.Username = "Username " + i.ToString();
				model.Status = (UserStatus)r.Next(1, 6);
				model.Homepage = "http://www.google.com";
				model.DisplayName = "DisplayName " + i.ToString();
				this.contacts.Add(model);

				if (model.Available) {
					this.contacts.Add(model);
				}
			}
			
			foreach (var user in contacts) {
				ChatModel model = new ChatModel(user);
				int limit = r.Next(10, 25);

				DateTime day = DateTime.Today + TimeSpan.FromMinutes(r.Next(0, 24 * 60));
				for (int i = 0; i < limit; i++) {
					MessageModel msg = new MessageModel(String.Format("Message {0}", i), day, (r.Next(0, 2) == 1));

					day += TimeSpan.FromMinutes(r.Next(0, 20));
					model.Messages.Add(msg);
				}
				this.chats.Add(model);
			}
		}
		/// <summary>
		/// Gets the messages for specified contact.
		/// </summary>
		/// <param name="model">The model.</param>
		/// <returns></returns>
		public ObservableCollection<MessageModel> GetMessages(ContactModel user) {
			if (user == null) {
				return null;
			}
			if (!this.messagesCache.ContainsKey(user)) {
				this.messagesCache[user] = new ObservableCollection<MessageModel>();
			}
			return this.messagesCache[user];
		}
		/// <summary>
		/// Gets the contact with the specified username.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <returns></returns>
		public ContactModel GetContact(String username) {
			if (String.IsNullOrEmpty(username)){
				return null;
			}
			foreach (var contact in this.contacts) {
				if (username == contact.Username) {
					return contact;
				}
			}
			return null;
		}
		/// <summary>
		/// Gets the chat for the specified username.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <returns></returns>
		public ChatModel GetChat(String username) {
			if (String.IsNullOrEmpty(username)) {
				return null;
			}
			var contact = this.GetContact(username);

			return GetChat(contact);
		}
		/// <summary>
		/// Gets the chat for the specified contact.
		/// </summary>
		/// <param name="contact">The contact.</param>
		/// <returns></returns>
		public ChatModel GetChat(ContactModel contact) {
			if (contact == null) {
				return null;
			}
			var list = (from n in this.chats
						where n != null && n.Contact != null && n.Contact == contact
						orderby n.Updated
						select n);
			foreach (var item in list) {
				return item;
			}


			var chat = new ChatModel(contact);
			this.chats.Insert(0, chat);
			
			this.OnPropertyChanged("Chats");
			return chat;
		}

		public void BeginGetMessages(ContactModel user, Action<ObservableCollection<MessageModel>> callback) {
			if (user == null) {
				return;
			}
			if (!this.messagesContainer.ContainsKey(user)) {
				this.messagesContainer[user] = new List<Action<ObservableCollection<MessageModel>>>();
			}

			this.messagesContainer[user].Add(callback);

			this.sync.SyncMessages(user.Username);
		}
		public void CancelGetMessages(ContactModel user, Action<ObservableCollection<MessageModel>> callback) {
			if (this.messagesContainer.ContainsKey(user)) {
				var list = this.messagesContainer[user];

				if (list.Contains(callback)) {
					list.Remove(callback);
				}
			}
		}
		#endregion
		#region Property
		public ContactModel[] Contacts {
			get {
				return this.contacts.ToArray();
			}
		}
		public ChatModel[] Chats {
			get {
				var list = (from item in this.chats
							orderby item.Updated
							select item).Take(5);
				return list.ToArray();
			}
		}
		public SkypeConfig Config {
			get { return this.config; }
		}
		public Boolean IsLoggedOn {
			get;
			set;
		}
		#endregion
		#region Events
		public event EventHandler ConnectionFailure;
		public event EventHandler ConnectionSuccess;
		#endregion
	}
}
